<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>自定义Vue_深度数据劫持</title>
</head>
<body>

  <script>
  // 定义Vue内部的实现
  function Vue(options) {
    // 将options中的data保存到Vue实例的_data上
    this._data = options.data

    // 实现数据代理
    this._proxy()

    // 实现深度数据劫持
    this._defineReactive(this._data)

    // 初始化methods
    this._initMethods(options.methods)
  }

  Vue.prototype = {

    /* 
    对指定data对象中所有层次属性定义为响应式属性
    */
    _defineReactive (data) {
      // 如果data不是对象, 直接结束
      if (data==null || typeof data!='object') return
      // 遍历对象中所有属性
      Object.keys(data).forEach(key => { // key是属性名
        // 取出属性值
        let value = data[key]
        // 递归调用, 实现所有层次属性的响应式
        this._defineReactive(value)
        // 利用defineProperty给data对象中当前key属性添加getter/setter
        Object.defineProperty(data, key, {
          // 监视读取data中的属性
          get () {
            console.log(`监视到读取${key}属性`)

            // 返回当前属性值
            return value
          },
          // 监视更新data中的属性
          set (newValue) {
            // 更新当前属性值
            value = newValue
            console.log(`监视到${key}属性更新为${newValue}`)
            console.log('准备去更新DOM')
          }
        })
      })
    },

    /* 
    将methods中所有的方法都添加到vm上
    将method中的this绑定为vm
    */
    _initMethods (methods) {
      // 遍历methods中每个method属性
      Object.keys(methods).forEach(key => {
        // this[key] = methods[key]  // 不能保证method中的this为vm
        this[key] = methods[key].bind(this) // 指定了this为vm
      })
    },

    // 实现数据代理的方法
    _proxy() {
      // 通过defineProperty给vue实例添加data中有所有属性
      // 遍历data中的所有属性, 分别去实现数据代理
      Object.keys(this._data).forEach(key => { // key就是msg/msg2
        // 给vue实例添加带getter和setter的属性
        Object.defineProperty(this, key, {
          // 在getter中: 读取并返回data对象中对应的属性值
          get () {
            console.log('getter')
            return this._data[key]
          },
          // 在setter中: 将最新的值保存到data对象的对应属性上
          set (value) {
            console.log('setter')
            this._data[key] = value
          }
        })
      })
    }
  }
  </script>

  <script>
    // 测试使用
    const vm = new Vue({
      data: {
        msg: 'abc',
        obj: {
          count: 2
        }
      },
      methods: {
        fn (){
          console.log('fn', this.msg)
        }
      }
    })

    // vm.fn()


    console.log(vm.msg) // 内部监视到了读取data的属性
    
    vm.msg = 'cba' // 内部监视到了更新data的外层属性

    vm.obj.count = 3 // 内部监视到了更新data的内层属性


  </script>
</body>
</html>